package com.xiaozhu.demo;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * java 反射机制之 Class类
 * @author Administrator
 * 在源程序中出现的类型，都有各自的Class实例对象 , 每个类在内存中只会生成一份字节码对象
 */
public class JavaReflectDemo1
{

	public static void main(String[] args) throws Exception
	{
		String str = "abc" ;
		/**
		 * 三中不同形式获取类型Class对象
		 */
		Class cls1 = str.getClass() ;
		Class cls2 = String.class ;
		Class cls3 = Class.forName("java.lang.String") ;
		/**
		 *  每个类都只会产生一个字节码对象
		 */
		System.out.println(cls1 == cls2);
		System.out.println(cls1 == cls3);
		// 判断类型是否是基本数据类型
		System.out.println(cls1.isPrimitive());
		System.out.println(int.class.isPrimitive());
		// 判断类型是否是数组类型
		System.out.println(int[].class.isArray());
		// 判断基本类型和类类型是否生成的是同一份字节码
		System.out.println(int.class == Integer.class); // false
		
		/**
		 * 通过Class类取得类的构造方法对象，通过构造方法对象获取类实例
		 */
		Constructor constructor1 = String.class.getConstructor(StringBuffer.class) ;
		String str1 = (String)constructor1.newInstance(new StringBuffer("abc")) ;
		System.out.println(str.charAt(2));
		
		/**
		 * 使用Class类获取字节码对象中的字段对象，通过字段对象获取字段的值
		 * getField()只能获取到public 修饰的字段对象
		 * 字段对象通过get(object)获取到对象中字段的值
		 */
		ReflectPoint point1 = new ReflectPoint(4, 6) ;
		Field fieldY = point1.getClass().getField("y") ;
		System.out.println(fieldY.get(point1));
		// 获取字节码对象中私有属性的字段对象
		Field fieldX = point1.getClass().getDeclaredField("x") ;
		// 设置私有字段对象可被访问
		fieldX.setAccessible(true);
		System.out.println(fieldX.get(point1));
		
		changeStringValue(point1) ;
		System.out.println(point1);
		
		/**
		 * 反射String类中的charAt方法
		 * 通过Class.getMethod()获取到Method对象
		 * 使用mentod.invoke()调用反射方法
		 */
		Method method1 = str.getClass().getMethod("charAt", int.class) ;
		System.out.println(method1.invoke(str, 1));
		
		System.out.println();
		/**
		 * 调用字节码对象中的main方法
		 * 向main方法中传递参数时，需要将String[]包装一下
		 * 在编译的时候会把String[]拆开，造成多个参数传入
		 */
		Method method2 = TestAguments.class.getMethod("main", String[].class) ;
		// 为null 时表示静态方法
		method2.invoke(null, (Object)new String[]{"111","222","333"}) ;
		
		ObjectArrayChange() ;
		System.out.println();
		
		String[] a1 = new String[]{"aaa","bb","c"} ;
		printObject(a1);
		System.out.println();
		int[] a2 = new int[]{1,2,34,4} ;
		printObject(a2);
	}

	/**
	 * 数组反射 , 打印数组对象
	 * @param object 数组对象
	 */
	private static void printObject(Object object)
	{
			Class _Class = object.getClass() ;
			
			if (_Class.isArray())
			{
				int len = Array.getLength(object) ;
				for(int i=0 ; i < len ; i++)
				{
					System.out.print(Array.get(object, i)+" ");
				}
			}
			else {
				System.out.println(object);
			}
	}

	/**
	 * 数组与Object之间的关系
	 */
	private static void ObjectArrayChange()
	{
		int[] a1 = new int[3];
		int[] a2 = new int[4] ;
		// 数组的类型与维数相同，则数组之间的字节码对象是相等的
		System.out.println(a1.getClass() == a2.getClass()); // true
		
		int[] a3 = new int[]{1,2,3,4} ;
		String[] a4 = new String[]{"a","b","c"} ;
		// 因为int类型数组是基本数据类型，asList传递的参数是Object
		// 所以int类型数组不能显示
		System.out.println(Arrays.asList(a3));
		System.out.println(Arrays.asList(a4));
	}

	/**
	 * 替换传入对象中String类型字段的值
	 * 将String类型字段中的字符a替换成字符b
	 * @param pObject 将替换的对象
	 * @throws Exception
	 */
	private static void changeStringValue(Object pObject) throws Exception
	{
		// 获取字节码对象中的所有字段
		Field[] fields = pObject.getClass().getFields() ;
		
		for(Field field : fields)
		{
			// 因为.class比较的是字节码对象，而字节码对象只会生成一个，所以使用==比较好，只对比值
			if (field.getType() == String.class)
			{
				// 获取字段对象值
				String oldValue = (String)field.get(pObject) ; 
				// 替换字段的值
				String newValue = oldValue.replace('a', 'b') ;
				// 设置字段对象值
				field.set(pObject, newValue);
			}
		}
	}

}

class TestAguments
{
	public static void main(String[] args)
	{
		for(String arg : args)
		{
			System.out.println(arg);
		}
	}
}
